package cn.uce.common.aop;

import cn.uce.common.annontion.Operation;
import cn.uce.common.log.biz.IOperationLogBiz;
import cn.uce.common.log.entity.OperationLog;
import cn.uce.common.utils.SpringContextUtil;
import cn.uce.core.mq.rocket.RocketTemplate;
import cn.uce.web.common.base.CurrentUser;
import cn.uce.web.common.util.WebUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;


public class LoggerAop implements MethodBeforeAdvice,AfterReturningAdvice,Ordered{

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private IOperationLogBiz iOperationLogBiz;

    private static final String CGLIB_STR = "$$EnhancerBySpringCGLIB$$";

    private RocketTemplate ccLoggerOutTemplate;
    /**
     * 过滤脱敏字段
     */
    private String[] FILTER_FIELDNAME = {"password"};
    private String FILTER_VALUE="***";

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        CurrentUser currentUser =  WebUtil.getCurrentUser();
        if(ObjectUtils.isEmpty(currentUser)){
            currentUser = new CurrentUser();
            currentUser.setEmpCode("-1");
            currentUser.setEmpName("系统");
            currentUser.setOrgId(0);
        }
        StringBuffer logMessage = null;
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        //Map<String, String> requestMap = this.getTypesafeRequestMap(request);
        BufferedRequestWrapper bufferedRequestWrapper = new BufferedRequestWrapper(request);
        String reqContentType = request.getContentType();
        String body = bufferedRequestWrapper.getRequestBody();
        // controller类
        Class<? extends Object> classzz = target.getClass();
        String clazzOperation = classzz.getName();
        //判断CGLIB代理
        if(clazzOperation.indexOf(CGLIB_STR)>=0){
            return;
        }
        String methodOperation = method.getName();
        Operation operation = AnnotationUtils.findAnnotation(method,Operation.class);
        if(null == operation){
            //方法没找到，再找类
            operation = AnnotationUtils.findAnnotation(method.getDeclaringClass(),Operation.class);;
        }
        if(operation instanceof Operation){
            methodOperation=operation.name();
        }
        if (null == operation){
            return;
        }
        //对requestbody进行处理
        //request body
        if(StringUtils.containsIgnoreCase(reqContentType, "json")){
            body = filterFieldName(body);
        }
        logMessage = new StringBuffer("REST Request - ")
                .append("[HTTP METHOD:")
                .append(request.getMethod())
                .append("] [PATH INFO:")
                .append(request.getPathInfo())
                .append("] [CONTENTTYPE INFO:")
                .append(reqContentType)
                //.append("] [REQUEST PARAMETERS:").append(requestMap)
                .append("] [REQUEST BODY:")
                .append(body)
                .append("] [REMOTE ADDRESS:")
                .append(request.getRemoteAddr()).append("]")
                .append(" [USER:empCode:").append(currentUser.getEmpCode())
                .append(",empName:")
                .append(currentUser.getEmpName())
                .append("]");
        try {
            logger.info(logMessage.toString());
            if(ccLoggerOutTemplate == null){
                ccLoggerOutTemplate = SpringContextUtil.getBean("ccLoggerOutTemplate");
            }
            OperationLog info =  new OperationLog(Thread.currentThread().getName(),request.getRemoteHost())
                    .setMessage(body)
                    .setOperationClass(clazzOperation)
                    .setOperationMethod(method.getName())
                    .setOperationName(methodOperation)
                    .setCreateOrg(currentUser.getOrgId())
                    .setCreateEmpCode(currentUser.getEmpCode())
                    .setCreateEmpName(currentUser.getEmpName())
                    .setLastTime(new Date());
            String sendStr = JSONObject.toJSONString(info);
            ccLoggerOutTemplate.send(sendStr,"CC_MAIN_FRONT",currentUser.getEmpCode());
        }
        catch (Exception e){
            logger.error("mq发送错误！",e);
        }finally {
        }
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
    }

    @Override
    public int getOrder() {
        return PriorityOrdered.HIGHEST_PRECEDENCE+1;
    }

    private static final class BufferedRequestWrapper extends HttpServletRequestWrapper {

        private ByteArrayInputStream bais = null;
        private ByteArrayOutputStream baos = null;
        private BufferedServletInputStream bsis = null;
        private byte[] buffer = null;

        public BufferedRequestWrapper(HttpServletRequest req) throws IOException {
            super(req);
            // Read InputStream and store its content in a buffer.
            this.buffer = inputStream2Byte(req.getInputStream());
            if (buffer.length == 0 && RequestMethod.POST.name().equals(req.getMethod())) {
                //从ParameterMap获取参数，并保存以便多次获取
                buffer = req.getParameterMap().entrySet().stream()
                        .map(entry -> {
                            String result;
                            String[] value = entry.getValue();
                            if (value != null && value.length > 1) {
                                result = Arrays.stream(value).map(s -> entry.getKey() + "=" + s)
                                        .collect(Collectors.joining("&"));
                            } else {
                                result = entry.getKey() + "=" + value[0];
                            }

                            return result;
                        }).collect(Collectors.joining("&")).getBytes("UTF-8");
            }
        }

        private byte[] inputStream2Byte(InputStream is) throws IOException {
            this.baos = new ByteArrayOutputStream();
            byte buf[] = new byte[1024];
            int read;
            while ((read = is.read(buf)) > 0) {
                this.baos.write(buf, 0, read);
            }
            return this.buffer = this.baos.toByteArray();
        }

        @Override
        public ServletInputStream getInputStream() {
            this.bais = new ByteArrayInputStream(this.buffer);
            this.bsis = new BufferedServletInputStream(this.bais);
            return this.bsis;
        }

        String getRequestBody() throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(this.getInputStream(),"UTF-8"));
            String line = null;
                StringBuilder inputBuffer = new StringBuilder();
            do {
                line = reader.readLine();
                if (null != line) {
                    inputBuffer.append(line.trim());
                }
            } while (line != null);
            reader.close();
            return inputBuffer.toString().trim();
        }

    }

    private static final class BufferedServletInputStream extends ServletInputStream {

        private ByteArrayInputStream bais;

        public BufferedServletInputStream(ByteArrayInputStream bais) {
            this.bais = bais;
        }

        @Override
        public int available() {
            return this.bais.available();
        }

        @Override
        public int read() {
            return this.bais.read();
        }

        @Override
        public int read(byte[] buf, int off, int len) {
            return this.bais.read(buf, off, len);
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {

        }
    }

    public class TeeServletOutputStream extends ServletOutputStream {

        private final TeeOutputStream targetStream;

        public TeeServletOutputStream(OutputStream one, OutputStream two) {
            targetStream = new TeeOutputStream(one, two);
        }

        @Override
        public void write(int arg0) throws IOException {
            this.targetStream.write(arg0);
        }

        @Override
        public void flush() throws IOException {
            super.flush();
            this.targetStream.flush();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.targetStream.close();
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener writeListener) {
        }
    }

    public class BufferedResponseWrapper implements HttpServletResponse {

        HttpServletResponse original;
        TeeServletOutputStream tee;
        ByteArrayOutputStream bos;

        public BufferedResponseWrapper(HttpServletResponse response) {
            original = response;
        }

        public String getContent() {
            if (bos == null) {
                return "";
            }
            return bos.toString();
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            return original.getWriter();
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            if (tee == null) {
                bos = new ByteArrayOutputStream();
                tee = new TeeServletOutputStream(original.getOutputStream(), bos);
            }
            return tee;

        }

        @Override
        public String getCharacterEncoding() {
            return original.getCharacterEncoding();
        }

        @Override
        public String getContentType() {
            return original.getContentType();
        }

        @Override
        public void setCharacterEncoding(String charset) {
            original.setCharacterEncoding(charset);
        }

        @Override
        public void setContentLength(int len) {
            original.setContentLength(len);
        }

        @Override
        public void setContentLengthLong(long len) {
        }

        @Override
        public void setContentType(String type) {
            original.setContentType(type);
        }

        @Override
        public void setBufferSize(int size) {
            original.setBufferSize(size);
        }

        @Override
        public int getBufferSize() {
            return original.getBufferSize();
        }

        @Override
        public void flushBuffer() throws IOException {
            tee.flush();
        }

        @Override
        public void resetBuffer() {
            original.resetBuffer();
        }

        @Override
        public boolean isCommitted() {
            return original.isCommitted();
        }

        @Override
        public void reset() {
            original.reset();
        }

        @Override
        public void setLocale(Locale loc) {
            original.setLocale(loc);
        }

        @Override
        public Locale getLocale() {
            return original.getLocale();
        }

        @Override
        public void addCookie(Cookie cookie) {
            original.addCookie(cookie);
        }

        @Override
        public boolean containsHeader(String name) {
            return original.containsHeader(name);
        }

        @Override
        public String encodeURL(String url) {
            return original.encodeURL(url);
        }

        @Override
        public String encodeRedirectURL(String url) {
            return original.encodeRedirectURL(url);
        }

        @SuppressWarnings("deprecation")
        @Override
        public String encodeUrl(String url) {
            return original.encodeUrl(url);
        }

        @SuppressWarnings("deprecation")
        @Override
        public String encodeRedirectUrl(String url) {
            return original.encodeRedirectUrl(url);
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            original.sendError(sc, msg);
        }

        @Override
        public void sendError(int sc) throws IOException {
            original.sendError(sc);
        }

        @Override
        public void sendRedirect(String location) throws IOException {
            original.sendRedirect(location);
        }

        @Override
        public void setDateHeader(String name, long date) {
            original.setDateHeader(name, date);
        }

        @Override
        public void addDateHeader(String name, long date) {
            original.addDateHeader(name, date);
        }

        @Override
        public void setHeader(String name, String value) {
            original.setHeader(name, value);
        }

        @Override
        public void addHeader(String name, String value) {
            original.addHeader(name, value);
        }

        @Override
        public void setIntHeader(String name, int value) {
            original.setIntHeader(name, value);
        }

        @Override
        public void addIntHeader(String name, int value) {
            original.addIntHeader(name, value);
        }

        @Override
        public void setStatus(int sc) {
            original.setStatus(sc);
        }

        @SuppressWarnings("deprecation")
        @Override
        public void setStatus(int sc, String sm) {
            original.setStatus(sc, sm);
        }

        @Override
        public String getHeader(String arg0) {
            return original.getHeader(arg0);
        }

        @Override
        public Collection<String> getHeaderNames() {
            return original.getHeaderNames();
        }

        @Override
        public Collection<String> getHeaders(String arg0) {
            return original.getHeaders(arg0);
        }

        @Override
        public int getStatus() {
            return original.getStatus();
        }

    }

    /**
     * 过滤请求报文中的部分字段
     * @param requestMsg
     * @return
     */
    private String filterFieldName(String requestMsg){

        if(StringUtils.isNotBlank(requestMsg) && StringUtils.startsWith(requestMsg, "{")){
            JSONObject jsonObject = JSONObject.parseObject(requestMsg);
            for(String fieldName : FILTER_FIELDNAME){
                Object val = jsonObject.get(fieldName);
                if(val != null){
                    jsonObject.put(fieldName, FILTER_VALUE);
                }
            }
            return jsonObject.toString();
        }
        return requestMsg;
    }

    private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
        Map<String, String> typesafeRequestMap = new HashMap<String, String>();
        Enumeration<?> requestParamNames = request.getParameterNames();
        while (requestParamNames.hasMoreElements()) {
            String requestParamName = (String) requestParamNames.nextElement();
            String requestParamValue = request.getParameter(requestParamName);
            //日志中需要隐藏某些字段
            boolean hideField = false;
            for(String pwdName : FILTER_FIELDNAME){

                if(StringUtils.equals(requestParamName, pwdName)){
                    hideField = true;
                    break;
                }
            }

            if(hideField){
                typesafeRequestMap.put(requestParamName, FILTER_VALUE);
            }else{
                typesafeRequestMap.put(requestParamName, requestParamValue);
            }
        }
        return typesafeRequestMap;
    }
}
